package repositories;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.criterion.Restrictions;
import org.springframework.orm.hibernate3.HibernateCallback;

import users.Person;
import util.Pair;
import books.BookInfo;
import books.BookInfoHome;

/**
 */
public class BookInfoHomeRepository extends HibernateGenericDAO<BookInfoHome> implements
        GenericRepository<BookInfoHome> {

    private static final long serialVersionUID = 1L;

    @Override
    protected Class<BookInfoHome> getDomainClass() {
        return BookInfoHome.class;
    }

    @SuppressWarnings("unchecked")
    public List<BookInfo> getBookInfos() {
        return (List<BookInfo>) this.getHibernateTemplate().execute(new HibernateCallback<Object>() {
            @Override
            public List<BookInfo> doInHibernate(final Session session) throws HibernateException, SQLException {
                // Criteria criteria =
                // session.createCriteria(BookInfoHome.class);
                // criteria.add(Restrictions.like("name", "%" + pattern + "%"));
                Query query = session.createQuery("select bookInfo from BookInfoHome");
                return query.list();
            }
        });

    }

    public BookInfo findBookByIsbn(final String anIsbn) {
        return (BookInfo) this.getHibernateTemplate().execute(new HibernateCallback<Object>() {
            @Override
            public BookInfo doInHibernate(final Session session) throws HibernateException, SQLException {
                Criteria criteria = session.createCriteria(BookInfoHome.class);
                criteria.createAlias("bookInfo", "info").add(Restrictions.eq("info.isbn", anIsbn));

                BookInfoHome result = (BookInfoHome) criteria.uniqueResult();
                return result.getBookInfo();
            }
        });
    }

    public BookInfoHome getBookInfoHome(final BookInfo aBookInfo) {
        return (BookInfoHome) this.getHibernateTemplate().execute(new HibernateCallback<Object>() {
            @Override
            public BookInfoHome doInHibernate(final Session session) throws HibernateException, SQLException {
                Criteria criteria = session.createCriteria(BookInfoHome.class);

                criteria.createAlias("bookInfo", "info").add(Restrictions.eq("info.id", aBookInfo.getId()));
                return (BookInfoHome) criteria.uniqueResult();
            }
        });
    }

    public BookInfo findBookInfoById(final long id) {
        return (BookInfo) this.getHibernateTemplate().execute(new HibernateCallback<Object>() {
            @Override
            public BookInfo doInHibernate(final Session session) throws HibernateException, SQLException {
                Criteria criteria = session.createCriteria(BookInfoHome.class);
                criteria.createAlias("bookInfo", "info").add(Restrictions.eq("info.id", id));

                BookInfoHome result = (BookInfoHome) criteria.uniqueResult();
                return result.getBookInfo();
            }
        });
    }

    @SuppressWarnings("unchecked")
    public List<BookInfo> findByTitle(final String title) {

        return (List<BookInfo>) this.getHibernateTemplate().execute(new HibernateCallback<Object>() {
            @Override
            public List<BookInfo> doInHibernate(final Session session) throws HibernateException, SQLException {
                Criteria criteria = session.createCriteria(BookInfo.class);
                criteria.add(Restrictions.ilike("title", "%" + title + "%"));

                List<BookInfo> result = criteria.list();
                return result;
            }
        });
    }

    @SuppressWarnings("unchecked")
    public List<Pair<BookInfo, Integer>> getTopX(final Integer anX) {
        final List<Pair<BookInfo, Integer>> ret = new ArrayList<Pair<BookInfo, Integer>>();
        return (List<Pair<BookInfo, Integer>>) this.getHibernateTemplate().execute(new HibernateCallback<Object>() {
            @Override
            public List<Pair<BookInfo, Integer>> doInHibernate(final Session session) throws HibernateException,
                    SQLException {
                Query query = session.createQuery("select bookInfo, timesBorrowed from BookInfoHome");
                Iterator iter = query.iterate();
                while (iter.hasNext()) {
                    Object[] current = (Object[]) iter.next();
                    BookInfo bookInfo = (BookInfo) current[0];
                    Integer integer = (Integer) current[1];
                    ret.add(new Pair(bookInfo, integer));
                }
                return ret;
            }
        });
    }

    @SuppressWarnings("unchecked")
    public List<BookInfo> getTopBooks(final int maxResults) {
        return (List<BookInfo>) this.getHibernateTemplate().execute(new HibernateCallback<Object>() {
            @Override
            public List<BookInfo> doInHibernate(final Session session) throws HibernateException, SQLException {
                Query query = session.createQuery("select bi.bookInfo " + "from BookInfoHome bi "
                        + "order by bi.timesBorrowed desc");
                query.setMaxResults(maxResults);
                return query.list();
            }
        });
    }

    @SuppressWarnings("unchecked")
    public List<BookInfo> loanRelatedBooks(final BookInfo aBookInfo) {
        return (List<BookInfo>) this.getHibernateTemplate().execute(new HibernateCallback<Object>() {
            @Override
            public List<BookInfo> doInHibernate(final Session session) throws HibernateException, SQLException {
                Query queryLoan, queryRelatedBooks;

                queryLoan = session.createQuery("select l.loaner " + "from Loan l join l.loanedBook as b"
                        + " where b.info = :aBookInfo");

                queryLoan.setParameter("aBookInfo", aBookInfo);
                List<Person> loaners = queryLoan.list();

                Set<BookInfo> books = new HashSet<BookInfo>();

                queryRelatedBooks = session.createQuery("select b.info " + "from Loan l join l.loanedBook as b"
                        + " where l.loaner = :loaner");

                for (Person person : loaners) {

                    queryRelatedBooks.setParameter("loaner", person);
                    books.addAll(queryRelatedBooks.list());
                }
                if (books.contains(aBookInfo)) {
                    books.remove(aBookInfo);
                }
                return new ArrayList<BookInfo>(books);
            }
        });
    }

    @SuppressWarnings("unchecked")
    public List<BookInfo> getNewBooks(final int maxResult) {
        return (List<BookInfo>) this.getHibernateTemplate().execute(new HibernateCallback<Object>() {
            @Override
            public List<BookInfo> doInHibernate(final Session session) throws HibernateException, SQLException {
                Query query;
                query = session.createQuery("select bih.bookInfo " + "from BookInfoHome bih join bih.bookInfo as bi"
                        + " order by bi.registrationDate desc");

                query.setMaxResults(maxResult);
                return query.list();
            }
        });
    }

    @SuppressWarnings("unchecked")
    public List<BookInfo> searchByExample(final BookInfo example) {
        return (List<BookInfo>) this.getHibernateTemplate().execute(new HibernateCallback<Object>() {
            @Override
            public List<BookInfo> doInHibernate(final Session session) throws HibernateException, SQLException {
                Query query = session.createQuery("from BookInfo");
                Criteria criteria = session.createCriteria(BookInfo.class);
                if (example.getTitle() != null) {
                    query = session.createQuery("from BookInfo bi " + " where bi.title like :title");
                    query.setParameter("title", example.getTitle());
                    // criteria.add(Restrictions.ilike("title", example.getTitle(), MatchMode.ANYWHERE));

                }
                if (example.getAuthors() != null) {
                    // criteria.add(Restrictions.ilike("authors", example.getAuthors()));
                    query = session.createQuery("select bi " + "from BookInfo bi join bi.authors aut"
                            + " where bi.title like :title and aut.name like :authorName");
                    query.setParameter("title", example.getTitle());
                    query.setParameter("authorName", example.getAuthors().get(0).getName());
                }
                if (example.getCategories() != null) {
                    // criteria.add(Restrictions.ilike("authors", example.getAuthors()));
                    query = session.createQuery("select bi "
                            + "from BookInfo bi left join bi.authors aut left join bi.categories cat "
                            + " where bi.title like :title and aut.name like :authorName and cat like :cats");
                    query.setParameter("title", example.getTitle());
                    query.setParameter("authorName", example.getAuthors().get(0).getName());
                    query.setParameter("cats", example.getCategories());
                }
                // return criteria.list();
                return query.list();
            }
        });
    }

}
